﻿using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;

namespace Sai.Codeplex.Tasks
{
    /// <summary>
    /// Converts aml syntax help to codeplex wiki format.
    /// </summary>
    public class AmlToWiki : Task
    {
        const string TopicNodeName = "topic";
        const string DeveloperConceptualDocumentNodeName = "developerConceptualDocument";
        const string IntroductionNodeName = "introduction";
        const string SectionNodeName = "section";
        const string TitleNodeName = "title";
        const string ContentNodeName = "content";
        const string ParaNodeName = "para";
        const string TableNodeName = "table";
        const string TableHeaderNodeName = "tableHeader";
        const string RowNodeName = "row";
        const string EntryNodeName = "entry";
        const string SystemNodeName = "system";
        const string CommandNodeName = "command";
        const string InternalLinkNodeName = "link";
        const string ExternalLinkNodeName = "externalLink";
        const string LinkTextNodeName = "linkText";
        const string LinkUriNodeName = "linkUri";

        const string ListNodeName = "list";
        const string ListItemNodeName = "listItem";
        const string CodeNodeName = "code";

        private bool inTable = false;
        private bool inTableHeader = false;
        private bool firstEntryInRow = false;
        private bool inRow = false;
        private bool inEntry = false;

        private bool inList = false;
        private bool inListItem = false;

        const string WelcomePageGuid        = "3e2bef8a-6d00-45f8-9943-b838a7bffa68";
        const string WelcomePageName        = "Welcome";
        const string DocumentationPageGuid  = "51c2899b-41ee-400e-a92b-e47df82cad97";
        const string DocumentationPageName  = "Documentation";
        const string CommandLineGuid        = "f595059f-1b22-49cb-ac25-76a866e1252e";
        const string CommandLineName        = "Running From The Command Prompt";
        const string MsBuildGuid            = "f2cf5b5d-6c5b-416b-9e89-88789a4680e0";
        const string MsBuildName            = "Running From MsBuild";
        const string GuiGuid                = "09045013-8805-4847-96a2-5ce8af2e9132";
        const string GuiName                = "GUI";
        const string ReportsGuid            = "9d64bd02-7248-451a-9f54-854c9d55f1a1";
        const string ReportsName            = "Reports";

        [Required]
        public ITaskItem[] AmlFiles { get; set; }

        [Required]
        public ITaskItem OutputFolder { get; set; }

        public override bool Execute()
        {
            var amlFiles = new List<string>();

            if (this.AmlFiles.Length > 0)
            {
                for (int i = 0; i < this.AmlFiles.Length; ++i)
                {
                    ITaskItem item = this.AmlFiles[i];
                    string amlFile = item.GetMetadata("FullPath");

                    if (File.Exists(amlFile))
                    {
                        amlFiles.Add(amlFile);
                    }
                }
            }

            if (!amlFiles.Any())
            {
                Log.LogWarning("No aml files found to transform");
            }

            string outputFolder = string.Empty;

            if (this.OutputFolder != null)
            {
                outputFolder = this.OutputFolder.GetMetadata("FullPath");
            }

            if (!string.IsNullOrEmpty(outputFolder))
            {
                if (!Directory.Exists(outputFolder))
                {
                    Log.LogMessage("Creating folder: \"{0}\"", outputFolder);
                    Directory.CreateDirectory(outputFolder);
                }
            }

            Encoding fileEncoding = Encoding.UTF8;
            const bool append = false;

            foreach (var amlFile in amlFiles)
            {
                string wikiFile = Path.Combine(outputFolder, Path.GetFileNameWithoutExtension(amlFile) + ".wiki");

                string wikiContent = AmlToWikiFile(amlFile);

                using (TextWriter writer = new StreamWriter(wikiFile, append, fileEncoding))
                {
                    Log.LogMessage("Transforming \"{0}\" to \"{1}\"", amlFile, wikiFile);
                    writer.Write(wikiContent);
                }
            }

            return true;
        }

        private string AmlToWikiFile(string amlFilePath)
        {
            StringBuilder builder = new StringBuilder();

            try
            {
                XmlDocument amlDoc = new XmlDocument();
                amlDoc.Load(amlFilePath);

                foreach (XmlNode child in amlDoc.ChildNodes)
                {
                    HandleNode(child, builder);
                }
            }
            catch(Exception ex)
            {
                Log.LogErrorFromException(ex);
            }

            builder.Replace("\t", " ");
            builder.Replace("  ", " ");
            builder.Replace("  ", " ");

            return builder.ToString();
        }

        private string LinkGuidToWikiName(string guid)
        {
            Dictionary<string, string> knownValues = new Dictionary<string, string>();

            knownValues.Add(WelcomePageGuid, WelcomePageName);      
            knownValues.Add(DocumentationPageGuid, DocumentationPageName);
            knownValues.Add(CommandLineGuid, CommandLineName);
            knownValues.Add(MsBuildGuid, MsBuildName);
            knownValues.Add(GuiGuid, GuiName);
            knownValues.Add(ReportsGuid, ReportsName);
            
            if (knownValues.ContainsKey(guid))
            {
                return knownValues[guid];
            }
            
            return string.Empty;
        }

        private void HandleNode(XmlNode node, StringBuilder builder)
        {
            if (node.NodeType == XmlNodeType.XmlDeclaration)
                return;

            if (node.NodeType == XmlNodeType.Text || node.NodeType == XmlNodeType.CDATA)
            {
                string text = node.Value.Trim();

                builder.Append(text);
                return;
            }

            foreach (XmlNode child in node.ChildNodes)
            {
                if (child.NodeType == XmlNodeType.Text || child.NodeType == XmlNodeType.CDATA)
                {
                    string text = child.Value.Trim();
                    builder.Append(text);
                    continue;
                }

                switch (child.Name)
                {
                    case DeveloperConceptualDocumentNodeName:

                        HandleNode(child, builder);
                        break;

                    case IntroductionNodeName:

                        builder.AppendLine();
                        //builder.Append("! ");
                        HandleNode(child, builder);
                        break;

                    case ParaNodeName:

                        HandleNode(child, builder);

                        if (!inTable)
                        {
                            builder.AppendLine();
                        }
                        break;

                    case SectionNodeName:

                        builder.AppendLine();
                        builder.AppendLine();
                        HandleNode(child, builder);
                        builder.AppendLine();
                        break;

                    case TitleNodeName:

                        if (inTable)
                        {
                            builder.AppendLine();
                            builder.Append("!!! ");
                            HandleNode(child, builder);
                            builder.AppendLine();
                        }
                        else
                        {
                            builder.Append("!! ");
                            HandleNode(child, builder);
                            builder.AppendLine();
                        }

                        break;

                    case ContentNodeName:

                        builder.AppendLine();
                        HandleNode(child, builder);
                        builder.AppendLine();
                        break;

                    case TableNodeName:

                        inTable = true;
                        HandleNode(child, builder);
                        inTable = false;
                        break;

                    case TableHeaderNodeName:

                        inTableHeader = true;

                        HandleNode(child, builder);

                        inTableHeader = false;
                        break;

                    case RowNodeName:

                        inRow = true;
                        firstEntryInRow = true;

                        HandleNode(child, builder);

                        if (inTableHeader)
                        {
                            builder.AppendLine("||");
                        }
                        else
                        {
                            builder.AppendLine("|");
                        }

                        inRow = false;
                        break;

                    case EntryNodeName:

                        if (firstEntryInRow && inTableHeader)
                        {
                            firstEntryInRow = false;

                            builder.Append("|");
                        }

                        builder.Append("| ");
                        HandleNode(child, builder);
                        builder.Append(" ");

                        break;

                    case SystemNodeName:

                        builder.Append("*");
                        HandleNode(child, builder);
                        builder.Append("*");
                        break;

                    case CommandNodeName:

                        HandleNode(child, builder);
                        break;

                    case InternalLinkNodeName:


                        string linkGuid = string.Empty;
                        string linkText = child.InnerText;

                        foreach (XmlAttribute attr in child.Attributes)
                        {
                            if (attr.Name == "xlink:href")
                            {
                                linkGuid = attr.Value.ToLower();
                            }
                        }

                        if (!string.IsNullOrEmpty(linkGuid))
                        {
                            string linkPage = LinkGuidToWikiName(linkGuid);

                            // need to do lookup...
                            // emit link 
                            builder.AppendFormat("[{0}|{1}] ", linkText, linkPage);
                        }

                        break;

                    case ExternalLinkNodeName:


                        string linkName = string.Empty;
                        string linkUrl = string.Empty;

                        // needs to be in specific order...
                        foreach (XmlNode links in child.ChildNodes)
                        {
                            if (links.Name == LinkTextNodeName)
                                linkName = links.InnerText;
                            else if (links.Name == LinkUriNodeName)
                                linkUrl = links.InnerText;
                        }

                        builder.AppendFormat(" [url:{0}|{1}] ", linkName, linkUrl);
                        break;

                    case ListNodeName:

                        inList = true;
                        
                        // bullet or number ???
                        //foreach (XmlAttribute attr in child.Attributes)
                        //{
                        //}

                        HandleNode(child, builder);
                        inList = false;
                        break;

                    case ListItemNodeName:

                        inListItem = true;

                        builder.Append("* ");
                        HandleNode(child, builder);

                        inListItem = false;
                        break;

                    case CodeNodeName:

                        string codeLanguage = string.Empty;

                        foreach (XmlAttribute attr in child.Attributes)
                        {
                            if (attr.Name == "language")
                            {
                                codeLanguage = attr.Value.ToLower();
                            }
                        }

                        builder.AppendFormat("{{code:{0}}}", codeLanguage);
                        builder.AppendLine();
                        HandleNode(child, builder);
                        builder.AppendLine();
                        builder.AppendFormat("{{code:{0}}}", codeLanguage);
                        builder.AppendLine();
                        break;

                    default:

                        builder.AppendLine();
                        builder.AppendLine("** Unknown tag: " + child.Name);
                        HandleNode(child, builder);
                        break;
                }
            }
        }
    }
}
